﻿#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
// 一. 整数在内存中的存储

//1.原码反码补码

// 在计算机中整数在内存中存储的是二进制数

// 二进制的存储有三种表示的方式:

//1.原码
//2.反码
//3.补码

 //这三种表示方式又分为符号位和数值位：

 //符号位中0表示正数，1表示负数，最高位被当作符号位，其他为数值位。

 /*如：1  和  -1  -32位展示
 1：00000000000000000000000000000001

-1：10000000000000000000000000000001*/

//原码，反码，补码解释：
// 如：5
// 00000000 00000000 00000000 0000101 - 原码
// 01111111 11111111 11111111 1111010 - 反码
// 01111111 11111111 11111111 1111011 - 补码

//相互转换规则：
// 原码 -> 反码 ：符号位不变，其他取反。 取反 1->0 或者 0->1
// 反码 -> 补码 ：+1
// 原码 -> 补码 ：取反+1
// 补码 -> 原码 ：取反+1

//而对于数据在内存中存储的是补码。






// 2.大小端字节序和字节判断
#if 0
int main()
{
	int a = 0x11223344;
	return 0;
}
#endif
//可以看出在内存中存储的是 44 33 22 11 正好和我们的反过来了，这是什么原因呢？ - vs底层使用的是小端存储

// 什么是大小端？
// 首先我们要明白一个数据在内存中存储是有顺序的。
//
//大端存储：数据的低位字节保存在内存的高地址处，高位字节保存在内存的低地址处。
//
//小端存储：数据的低位字节保存在内存的低地址处，高位字节保存在内存的高地址处。

//什么是低位字节，我们拿一个数值来类比一下：
// 56   6->个位，所以6是低位， 5->十位所以5是高位
// 0x11223344 也是如此，  44 -> 低位    11 -> 高位


// 判断当前机器是大端存储还是小端存储
#if 0
int check()
{
	int i = 1;
	return (*(char*)&i); //cahr* 修饰拿到一个字节的存储
		
}

int main()
{
	int ret = check();
	if (ret == 1)
		printf("小端");
	else
		printf("大端");
	return 0;
}
#endif

// 练习1
#if 0
#include <stdio.h>
int main()
{
	// -1 10000000000000000000000000000001 -原码
	//    11111111111111111111111111111110 -反码
	//    11111111111111111111111111111111 -补码

	char a = -1;
	// 以%d形式打印，在vs中char是有符号类型，由于是负数，先取反+1，最后结果还是 -1

	signed char b = -1;
	// 以%d形式打印，是有符号类型，由于是负数，先取反+1，最后结果还是 -1

	unsigned char c = -1;
	// 以%d形式打印，是无符号类型为整数，补码就是原码，但由于unsigned char 范围是 0~255 ，所以打印最大255

	printf("a=%d,b=%d,c=%d", a, b, c);
	return 0;
}
#endif

//练习2
#if 0
#include <stdio.h>
int main()
{
	// -128 - 10000000000000000000000010000000 -原码
	//        11111111111111111111111101111111 -反码
	//        11111111111111111111111110000000 -补码
	char a = -128;
	char b =  128;
	// %d - 有符号的十进制输出     %u - 无符号的十进制输出
	printf("%u\n", a); // a = 4,294,967,168
	printf("%u\n", b); // b = 4,294,967,168
	return 0;
}
#endif

//练习3
#if 0
#include <stdio.h>
int main()
{
	char a[1000];

	int i;

	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i;   
	}
	// -1 -2 -3 -4 -5 .....  -127  128 127 126 .... 0  -1 -2 -3...
	printf("%d", strlen(a)); // a=225

	return 0;
}
#endif

//练习4
#if 0
#include <stdio.h>
unsigned char i = 0; //char - -127~128   unsigned char - 0~255
int main()
{
	for (i = 0; i <= 255; i++)
	{
		printf("hello world\n");  //死循环
	}
	return 0;
}
#endif

#if 0
#include <stdio.h>
int main()
{
	unsigned int i;  //unsigned int -  0~4294967295
	for (i = 9; i >= 0; i--) // i  min=0 这边的循环就会一直走
	{
		printf("%u\n", i);
	}
	return 0;
}
#endif


 /*练习5
 x86环境，小端环境*/
#if 0
#include <stdio.h>
int main()
{
	int a[4] = { 1, 2, 3, 4 };
	int* ptr1 = (int*)(&a + 1); //&a - 取出的是整个数组的地址
	int* ptr2 = (int*)((int)a + 1);
	printf("%#x,%#x", ptr1[-1], *ptr2);// ptr1 = 4  ptr2 = 0x02 00 00 00
	return 0;
}
#endif



// 3.浮点数在内存中的存储

 //常见的浮点数: 3.14159  1E10(1.0 * 10^10)
 //浮点数包含：float / double  / long dlouble

// 浮点数的存储
//根据国际标准IEEE（电⽓和电⼦⼯程协会）754，任意⼀个⼆进制浮点数V可以表⽰成下⾯的形式：

//V = (−1)∗ S M ∗ 2E 
//• (−1)^S 表⽰符号位，当S = 0，V为正数；当S = 1，V为负数
//• M表⽰有效数字，M是⼤于等于1，⼩于2的
//• 2^E 表⽰指数位

//举例;
//十进制的5.0 ，二进制是 101.0 ，相当于 1.01*（2^2）
//按照格式来写的话，首先是正数 S=0 , M=1.01. E=2。
//   负数5则是->S = 1, M = 1.01.E = 2。

///*IEEE754规定：
//1.对于32位的浮点数，最⾼的1位存储符号位S，
//接着的8位存储指数E，剩下的23位存储有效数字M
//
//2.对于64位的浮点数，最⾼的1位存储符号位S，
//接着的11位存储指数E，剩下的52位存储有效数字M*/
//
///*IEEE754对有效数字M和指数E，还有⼀些特别规定。
//前⾯说过， 1≤ M < 2 ，也就是说，M可以写成 1.xxxxxx 的形式，
//其中 xxxxxx 表⽰⼩数部分。IEEE754规定，在计算机内部保存M时，默认这个数的第⼀位总是1，因此可以被舍去，只保存后⾯的
//xxxxxx部分。⽐如保存1.01的时候，只保存01，等到读取的时候，再把第⼀位的1加上去。这样做的⽬的，是节省1位有效数字。
//以32位浮点数为例，留给M只有23位，将第⼀位的1舍去以后，等于可以保存24位有效数字。*/
//
///*⾄于指数E，情况就⽐较复杂
//⾸先，E为⼀个⽆符号整数（unsigned int）
//这意味着，如果E为8位，它的取值范围为0~255；如果E为11位，它的取值范围为0~2047。但是，我
//们知道，科学计数法中的E是可以出现负数的，所以IEEE754规定，存⼊内存时E的真实值必须再加上
//⼀个中间数，对于8位的E，这个中间数是127；对于11位的E，这个中间数是1023。⽐如，2 ^ 10的E是
//10，所以保存成32位浮点数时，必须保存成10 + 127 = 137，即10001001。*/
//
//指数E从内存中取出还可以再分成三种情况：
//
// 1.E不全为0或不全为1
///*这时，浮点数就采⽤下⾯的规则表⽰，即指数E的计算值减去127（或1023），得到真实值，再将有效
//数字M前加上第⼀位的1。
//⽐如：0.5的⼆进制形式为0.1，由于规定正数部分必须为1，即将⼩数点右移1位，则为1.0 * 2 ^ (-1)，其
//阶码为 - 1 + 127(中间值) = 126，表⽰为01111110，⽽尾数1.0去掉整数部分为0，补⻬0到23位
//00000000000000000000000，则其⼆进制表⽰形式为:*/
//
// 0 01111110 00000000000000000000000
//
//
// 2.E全为0
///*这时，浮点数的指数E等于1-127（或者1-1023）即为真实值，有效数字M不再加上第⼀位的1，⽽是还
//原为0.xxxxxx的⼩数。这样做是为了表⽰±0，以及接近于0的很⼩的数字。*/
//
// 0 00000000 00100000000000000000000
//
//
// 3.E全为1
//这时，如果有效数字M全为0，表⽰±⽆穷⼤（正负取决于符号位s）
//
// 0 11111111 00010000000000000000000


// 题目解析
#if 0
#include <stdio.h>
int main()
{
	int n = 9;
	float* pFloat = (float*)&n;
	printf("n的值为：%d\n", n); // n = 9
	printf("*pFloat的值为：%f\n", *pFloat); // 000000
	// 9.0 - S = 0, M = 1001 , E = 0000 
	// V = (-1)^0 * 0.00000000000000000001001 * 2 ^ (1-127）= 1.001*2^(-146)
	// 二进制的形式 0 10000010 001 0000 0000 0000 0000 0000
	*pFloat = 9.0;
	printf("num的值为：%d\n", n); //1091567616
	printf("*pFloat的值为：%f\n", *pFloat);//9.0
	return 0;
}
#endif

// atoi 函数的使用和实现

//atoi 函数的作用 - 将字符串转换为整数 ,在遇到数字之前遇到空格会自动跳过向后寻找数字，如果找到非数字则返回0，如果找到数字则会继续向
//下寻找，直到遇到非数字返回

//需要包含头文件 #include <stdlib.h>

//如：char str[]="1234";  --> 1234

// int atoi(const char* str);
int my_atoi(const char* str)
{
	assert(str);
	const char* tmp = str;
	//跳过空格字符
	while(*tmp == ' ')
		tmp++;

	int num = 0;

	while (*tmp <= 57 && *tmp >= 48)
	{
		num = num * 10 + (*tmp - '0');

		if (*(tmp + 1) < 48 || *(tmp + 1) > 57)
		{
			return num;
		}

		tmp++;
	}

	return 0;
}

int main()
{
	char str1[] = "   1234";
	char str2[] = "@1234";
	char str3[] = "1234ad";
	char str4[] = "12$!@.34";
	int ret1 = atoi(str1);
	int ret2 = atoi(str2);
	int ret3 = atoi(str3);
	int ret4 = atoi(str4);
	printf("ret1 = %d\n", ret1);
	printf("ret2 = %d\n", ret2);
	printf("ret3 = %d\n", ret3);
	printf("ret4 = %d\n", ret4);
	int ret5 = my_atoi(str1);
	int ret6 = my_atoi(str2);
	int ret7 = my_atoi(str3);
	int ret8 = my_atoi(str4);
	printf("ret5 = %d\n", ret5);
	printf("ret6 = %d\n", ret6);
	printf("ret7 = %d\n", ret7);
	printf("ret8 = %d\n", ret8);
}